%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     角色在线管理器
%%% @end
%%%-------------------------------------------------------------------
-module(role_online_mgr).

-behaviour(gen_server).

-export([
    newday/0

    ,join/1
    ,leave/1
    ,logout/2

    ,size/0
    ,save_req/1
    ,stop_all_role/0
    ,send/1
    ,pack_send/2
    ,apply/1
]).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include("common.hrl").
-include("link.hrl").
-include("role.hrl").
-include("role_online.hrl").

-record(state, {queue = []}).
-record(save_req, {rid, pid}).

-define(timer_save, erlang:send_after(200, self(), timer_save)).

-define(save_each_count, 2).


%%%===================================================================
%%% API functions
%%%===================================================================

newday() ->
    Fun = fun(#role_online{pid = Pid}) -> role:newday(Pid) end,
    lists:foreach(Fun, ets:tab2list(role_online)).


join(#role{pid = RolePid, rid = Rid, account = Account, name = Name, link = #link{conn_pid = ConnPid}}) ->
    RoleOnline = #role_online{
        rid = Rid
        ,pid = RolePid
        ,conn_pid = ConnPid
        ,account = Account
        ,name = Name
    },
    ets:insert(role_online, RoleOnline),
    ok.

leave(#role{rid = Rid}) ->
    leave(Rid);
leave(Rid) ->
    ets:delete(role_online, Rid),
    ?MODULE ! {leave, Rid}.

logout(#role{rid = Rid}, _EventData) ->
    leave(Rid),
    ok.


size() ->
    ets:info(role_online, size).

save_req(#role{rid = Rid, pid = Pid}) ->
    ?MODULE ! {join, Rid, Pid}.

stop_all_role() ->
    Fun = fun(#role_online{pid = Pid}) -> role:stop(sync, Pid) end,
    lists:foreach(Fun, ets:tab2list(role_online)).

send(Bin) ->
    spawn(fun() -> do_send(ets:tab2list(role_online), Bin) end).

pack_send(Cmd, Data) ->
    case util_net:pack(Cmd, Data) of
        {ok, Bin} -> send(Bin);
        {error, Reason} -> ?ERR("打包数据出错[Cmd:~w][Reason:~w]", [Cmd, Reason])
    end.

apply(Mfa)->
    spawn(fun() -> do_apply(ets:tab2list(role_online), Mfa) end).


%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).


init([]) ->
    process_flag(trap_exit, true),
    ets:new(role_online, [set, named_table, public, {keypos, #role_online.rid}]),
    State = #state{},
    ?timer_save,
    {ok, State}.

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info({join, RoleId, Pid}, State = #state{queue = Queue}) ->
    NewQueue =
        case lists:keyfind(RoleId, #save_req.rid, Queue) of
            false -> Queue ++ [#save_req{rid = RoleId, pid = Pid}];
            _ -> Queue
        end,
    {noreply, State#state{queue = NewQueue}};

handle_info({leave, RoleId}, State = #state{queue = Queue}) ->
    NewQueue = lists:keydelete(RoleId, #save_req.rid, Queue),
    {noreply, State#state{queue = NewQueue}};

handle_info(timer_save, State = #state{queue = Queue}) ->
    ?timer_save,

    NewQueue = save_role(Queue, 1),
    {noreply, State#state{queue = NewQueue}};

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%%===================================================================
%%% Internal functions
%%%===================================================================

save_role(Queue, Index) when Index > ?save_each_count -> Queue;
save_role([], _) -> [];
save_role([#save_req{pid = Pid} | T], Index) ->
    role:save(Pid),
    save_role(T, Index + 1).

do_send([], _Bin) -> ok;
do_send([#role_online{conn_pid = ConnPid} | T], Bin) ->
    ConnPid ! {send, Bin},
    do_send(T, Bin).

do_apply([], _Mfa) -> ok;
do_apply([#role_online{pid = RolePid} | T], Mfa)->
    role:apply(async, RolePid, Mfa),
    do_apply(T, Mfa).
